/*
 * Copyright (C) 2012  Romain DUBOIS
 * 
 * This file is part of NeMoS - Network and Modular Software
 * 
 * NeMoS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * NeMoS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with NeMoS.  If not, see <http://www.gnu.org/licenses/>.
 */
package nemos.launch.internal;

import java.util.Arrays;

import nemos.launch.progress.IProgressDisplay;
import nemos.launch.progress.IProgressListener;
import nemos.launch.progress.IRecords;
import nemos.launch.progress.IStatus;

import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;

/**
 * Converter : The progress converter service.
 * 
 * <p>
 * This class converts the current progress into a [0-100] value and notifies
 * {@link IProgressDisplay} services.
 * </p>
 * 
 * @author Romain DUBOIS
 */
public class Converter implements IProgressListener {

    /** The launch labels property name */
    private static final String LAUNCH_LABELS_PROPERTY = "nemos.launch.labels"; //$NON-NLS-1$
    /** The labels separator */
    private static final String REGEX = "\\\\"; //$NON-NLS-1$

    /** The current step of the last call */
    int mCurrentStep;
    /** The level labels */
    String[] mLabels;
    /** The current progress */
    int mProgress;
    /** The active flag */
    private boolean mActive;
    /** The progress scale */
    private long[] mProgressScale;
    /** The launch records */
    private IRecords mRecords;
    /** The service registration */
    private final ServiceRegistration<IProgressListener> mRegistration;
    /** The launch step times */
    private long[] mSteps;
    /** The progress display services tracker */
    private final ServiceTracker<IProgressDisplay, IProgressDisplay> mTracker;

    /**
     * Constructor
     * 
     * @param pContext
     *            The bundle context
     */
    public Converter(final BundleContext pContext) {
        super();

        mCurrentStep = -1;
        mProgress = -1;

        final String lLabelsProperty = pContext.getProperty(LAUNCH_LABELS_PROPERTY);
        if (lLabelsProperty == null) {
            mLabels = new String[0];
        } else {
            mLabels = lLabelsProperty.split(REGEX);
            for (int i = 0; i < mLabels.length; i++) {
                mLabels[i] = mLabels[i].trim();
            }
        }

        mTracker = new ServiceTracker<IProgressDisplay, IProgressDisplay>(pContext,
                IProgressDisplay.class, null) {

            @Override
            public IProgressDisplay addingService(
                    final ServiceReference<IProgressDisplay> pReference) {
                final IProgressDisplay lService = super.addingService(pReference);

                // Init label
                if (mCurrentStep < mLabels.length) {
                    lService.setProgressValue(mProgress);
                    lService.setLabel(mLabels[Math.max(0, mCurrentStep)]);
                }

                return lService;
            }
        };

        // Register service once initialisation ended
        mRegistration = pContext.registerService(IProgressListener.class, this, null);

        mActive = true;
    }

    /**
     * {@inheritDoc}
     * 
     * @see nemos.launch.progress.IProgressListener#ended()
     */
    @Override
    public synchronized void ended() {
        if (mActive) {
            String lEndLabel = null;
            if (mLabels.length > 0) {
                lEndLabel = mLabels[mLabels.length - 1];
            }
            update(100, lEndLabel);

            mRegistration.unregister();
            mTracker.close();
        }
        mActive = false;
    }

    /**
     * {@inheritDoc}
     * 
     * @see nemos.launch.progress.IProgressListener#init(nemos.launch.progress.IRecords,
     *      int)
     */
    @Override
    public void init(final IRecords pRecords, final int pTargetLevel) {
        mRecords = pRecords;

        // Init previous step times
        mSteps = new long[pTargetLevel + 1];
        mSteps[0] = 0;
        for (int i = 0; i < pTargetLevel; i++) {
            // Step time in previous launches plus the previous step time
            mSteps[i + 1] = mSteps[i] + mRecords.getTime(i + 1);
        }
        // Init matching array : progress -> time
        mProgressScale = new long[100];
        for (int i = 0; i < mProgressScale.length; i++) {
            mProgressScale[i] = (mSteps[pTargetLevel] * i) / mProgressScale.length;
        }

        mTracker.open();
        update(0, null);
    }

    /**
     * {@inheritDoc}
     * 
     * @see nemos.launch.progress.IProgressListener#progressChanged(nemos.launch.progress.IStatus)
     */
    @Override
    public void progressChanged(final IStatus pStatus) {
        String lLabel = null;

        // Update label
        if (pStatus.getCurrentLevel() != mCurrentStep) {
            mCurrentStep = pStatus.getCurrentLevel();

            if (mCurrentStep > 0 && mCurrentStep <= mLabels.length) {
                lLabel = mLabels[mCurrentStep];
            }
        }

        updateInCurrentStep(pStatus.getStartedBundles(), pStatus.getBundlesToStart(), lLabel);
    }

    /**
     * Update the progress in the surrent start level.
     * 
     * @param pCurrent
     *            The current local progress
     * @param pTotal
     *            The local progress max
     * @param pLabel
     *            The new label
     */
    void updateInCurrentStep(final int pCurrent, final int pTotal, final String pLabel) {
        // Get previous steps time
        long lProgress = mSteps[mCurrentStep];

        // Add partial current start level progress
        final long lTime = mRecords.getTime(mCurrentStep);
        if (pTotal > 0) {
            lProgress += ((pCurrent * lTime) / pTotal);
            lProgress = Math.min(lProgress, mSteps[mCurrentStep + 1]);
        }

        // Convert into percent value
        int lPercent = Arrays.binarySearch(mProgressScale, lProgress);
        if (lPercent < 0) {
            // Take lowest value
            lPercent = Math.max(0, -(lPercent + 2));
        }

        update(lPercent, pLabel);
    }

    /**
     * Change the current progress and notify listeners if required.
     * 
     * @param pProgress
     *            The new progress
     * @param pLabel
     *            The new progress label
     */
    private void update(final int pProgress, final String pLabel) {
        final boolean isProgressIncreased = mProgress < pProgress;
        if (isProgressIncreased || pLabel != null) {
            mProgress = Math.max(mProgress, pProgress);

            final Object[] lServices = mTracker.getServices();
            if (lServices != null) {
                for (final Object lService : lServices) {
                    final IProgressDisplay lDisplay = (IProgressDisplay) lService;
                    if (lDisplay != null) {

                        // Notify only if needed
                        if (isProgressIncreased) {
                            lDisplay.setProgressValue(mProgress);
                        }
                        if (pLabel != null) {
                            lDisplay.setLabel(pLabel);
                        }
                    }
                }
            }
        }
    }
}
